import { encode, decode } from '@ygoe/msgpack'
import { Actions, funcs } from './gen'
export * from './gen'

type Promisify<T> = T extends (...args: infer A) => infer R ? (...args: A) => Promise<R> : never
type PromisifyAll<T> = { [K in keyof T]: Promisify<T[K]> }
type WSResponse = {
	data: string | ArrayBuffer
}
type Callback = (...value: any[]) => void
type ErrorCallback = (err: Error) => void

export type WSAPI = {
	open(url?: string): Promise<WebSocket>
	readonly state: number
	url?: string
} & PromisifyAll<Actions>

// 键：ID，值：WebSocket
const clients = new Map<string, WebSocket>(),
	resolves: Callback[] = [],
	rejects: ErrorCallback[] = []
let ws: WebSocket,
	id = 1,
	p: Promise<WebSocket>

export { resolves }

export const timeoutError = new Error('请求超时'),
	APIURL = `${location.hostname}:7451`,
	API = <WSAPI>{
		open(url) {
			if (API.state == WebSocket.CLOSED) {
				const w = ws = new WebSocket('ws://' + (url || APIURL));
				w.binaryType = 'arraybuffer'
				w.addEventListener('message', (e: WSResponse) => {
					if (typeof e.data == 'string')
						throw new Error(e.data)

					const [id, err, ...data] = decode(e.data, { multiple: true })
					if (err)
						rejects[id]?.(new Error(err))
					else
						resolves[id]?.(data)
					delete rejects[id]
					if (id)
						delete resolves[id]
				})
				p = new Promise(res => w.addEventListener('open', () => res(w)))
			}
			return p
		},
		get state() { return ws ? ws.readyState : WebSocket.CLOSED },
		url: localStorage.getItem('hostURL') || void 0
	}

/** API 函数 */
export type APIFuncs = Exclude<keyof WSAPI, 'state'>

/** 超时时间 */
const TimeoutMs = 10 * 1000

for (const func of funcs)
	API[func] = function (...args: any[]) {
		let rid = id++, arr = [rid, func, ...args]
		API.open(this.url).then(ws => ws.send(encode(arr, { multiple: true })))
		return new Promise<any>((res, rej) => {
			resolves[rid] = res
			rejects[rid] = rej
			if (func != 'browseFile' && func != 'post' && func != 'classifyFile')
				setTimeout(() => {
					if (rid in resolves) {
						delete resolves[rid]
						delete rejects[rid]
						rej(timeoutError) // 超时
					}
				}, TimeoutMs)
		})
	}